extern crate crypto;
use base64::prelude::*;
use rand::prelude::*;

use std::{
    env::temp_dir,
    ffi::OsString,
    path::{self, Path},
    time::{self},
};

use base64::engine::general_purpose::URL_SAFE;
use clipboard_rs::{Clipboard, ClipboardContext};
use serde::{Deserialize, Serialize};

pub const VERSION: &str = "1.0";

#[derive(Serialize, Deserialize)]
pub struct Clip {
    pub format: ClipFormats,
    pub files: Vec<ClipFile>,
}

#[derive(Serialize, Deserialize)]
pub enum ClipScenes {
    Common,
    Office,
    Browser
}

#[derive(Serialize, Deserialize)]
pub struct ClipMeta {
    clips: Vec<Clip>,
    scence: ClipScenes
}
impl ClipMeta {
    pub fn new() -> Self {
        Self {
            clips: Vec::new(),
            scence: ClipScenes::Common
        }
    }
    pub fn from_clips(clips: Vec<Clip>, scence: ClipScenes) -> ClipMeta {
        Self {
            clips,
            scence
        }
    }
    pub fn is_empty(self: &Self) -> bool {
        self.clips.is_empty()
    }
    pub fn get_clips(self: &Self) -> &Vec<Clip> {
        &self.clips
    }
}

#[derive(Serialize, Deserialize)]
pub struct ClipFile {
    pub filename: String,
    payload: String,
}

impl ClipFile {
    pub fn new(filename: &str, payload: &str) -> Self {
        let payload = if payload.is_empty() {
            String::new()
        } else {
            URL_SAFE.encode(payload.as_bytes())
        };

        Self {
            filename: filename.to_string(),
            payload: payload,
        }
    }

    pub fn set_raw(&mut self, payload: &str) {
        self.payload = payload.to_string()
    }

    pub fn get_raw(&self) -> String {
        self.payload.clone()
    }

    pub fn get_content(&self) -> String {
        let decoded: Vec<u8> = URL_SAFE.decode(&self.payload).unwrap();
        let decoded_slice = decoded.as_slice();
        String::from_utf8_lossy(decoded_slice).to_string()
    }
}

#[derive(PartialEq, Serialize, Deserialize)]
pub enum ClipFormats {
    Html,
    Text,
    RichText,
    Image,
    File,
}

impl Clip {
    pub fn new(format: ClipFormats) -> Self {
        Self {
            format,
            files: Vec::new(),
        }
    }
    pub fn from_text(text: &str) -> Self {
        Self {
            format: ClipFormats::Text,
            files: vec![ClipFile::new("plaintext.txt", text)],
        }
    }
}

pub fn extract_clips(context: &ClipboardContext) -> Vec<Clip> {
    let formats = &context.available_formats().unwrap();
    println!("possible formats: {:?}", formats);
    let mut clips: Vec<Clip> = Vec::new();

    if formats.iter().any(|e| e.contains("FileContents")) {
        let mut clip = Clip::new(ClipFormats::File);
        clip.files = context
            .get_files()
            .unwrap()
            .iter()
            .map(|e| {
                let os_str: OsString = e.into();
                let path = Path::new(&os_str);
                let filename = path
                    .file_name()
                    .unwrap()
                    .to_os_string()
                    .into_string()
                    .unwrap();
                ClipFile::new(filename.as_str(), e.as_str())
            })
            .collect();
        clips.push(clip);
    } else {
        if formats.iter().any(|e| e.contains("Rich Text Format")) {
            let mut clip = Clip::new(ClipFormats::RichText);
            let file_clip = ClipFile::new("temp.rtf", context.get_rich_text().unwrap().as_str());
            clip.files.push(file_clip);
            clips.push(clip);
        }

        if formats.iter().any(|e| e.contains("HTML Format")) {
            let mut clip = Clip::new(ClipFormats::Html);
            let file_clip = ClipFile::new("temp.html", context.get_html().unwrap().as_str());
            clip.files.push(file_clip);
            clips.push(clip);
        }

        if formats.iter().any(|e| e.contains("CF_TEXT")) {
            let clip = Clip::from_text(context.get_text().unwrap().as_str());
            clips.push(clip);
        }

        if formats.iter().any(|e| e.contains("CF_DIBV5")) {
            let mut clip = Clip::new(ClipFormats::Image);

            let timestamp = time::UNIX_EPOCH.elapsed().unwrap().as_millis();
            let image_filename = format!("image_temp_{}_{}.dat", timestamp, random::<i16>());
            let temp_dir = temp_dir().to_str().unwrap().to_string();
            let paths = [&temp_dir, image_filename.as_str()];
            let _target_path = paths.join(path::MAIN_SEPARATOR_STR);

            match context
                .get_buffer("PNG")
                // .and_then(|image| {
                //     return image.save_to_path(&target_path);
                // })
                // .and_then(|_| return read(&target_path).map_err(|e| e.into()))
            {
                Ok(content) => {
                    let encoded = URL_SAFE.encode(content.as_slice());
                    let mut file_clip = ClipFile::new(image_filename.as_str(), "");
                    file_clip.set_raw(&encoded);
                    clip.files.push(file_clip);
                    clips.push(clip);
                }
                Err(error) => {
                    eprintln!("reading image failed: {}", error);
                }
            }
        }
    }

    clips
}


pub fn extract_clip_meta(context: &ClipboardContext) -> ClipMeta {
    let formats = context.available_formats();
    let scence = guess_scence(formats.unwrap());
    let meta = ClipMeta::from_clips(extract_clips(context), scence);
    return meta;
}

fn guess_scence(_formats: Vec<String>) -> ClipScenes {
    ClipScenes::Common
}